/*
 * Linea.java
 *
 * Created on 17 de mayo de 2008, 06:17 PM
 *
 * To change this template, choose Tools | Template Manager
 * and open the template in the editor.
 */

package com.pragmaticminds.forrest.geometry;

/**
 *
 * @author Pablo
 */
public class Line {    
    
    // a line is defined by the formula: ay + bx + c = 0
    private double m_a; /*!< This is the a coefficient in the line ay + bx + c = 0 */
    private double m_b; /*!< This is the b coefficient in the line ay + bx + c = 0 */
    private double m_c; /*!< This is the c coefficient in the line ay + bx + c = 0 */

    /** Creates a new instance of Linea */
    public Line(double a, double b, double c) {
        this.m_a = a;
        this.m_b = b;
        this.m_c = c;
    }

    public double getACoefficient(){
        return m_a;
    }
    
    public double getBCoefficient(){
        return m_b;
    }
    
    public double getCCoefficient(){
        return m_c;
    }
    
    /*! This method creates a line given two points.
        \param pos1 first point
        \param pos2 second point
        \return line that passes through the two specified points. */
    public static Line makeLineFromTwoPoints(Position p1, Position p2){   
      // 1*y + bx + c = 0 => y = -bx - c
      // with -b the direction coefficient (or slope)
      // and c = - y - bx
      double dA, dB, dC;
      double dTemp = p2.getPositionX() - p1.getPositionX(); // determine the slope
      if( Math.abs(dTemp) < Function.EPSILON ){
        // ay + bx + c = 0 with vertical slope=> a = 0, b = 1
        dA = 0.0;
        dB = 1.0;
      }else{
        // y = (-b)x -c with -b the slope of the line
        dA = 1.0;
        dB = -(p2.getPositionY() - p1.getPositionY())/dTemp;
      }
      // ay + bx + c = 0 ==> c = -a*y - b*x
      dC =  - dA*p2.getPositionY()  - dB * p2.getPositionX();
      return new Line( dA, dB, dC );
    }
    
    /*! This method creates a line given a position and an angle.
        \param vec position through which the line passes
        \param angle direction of the line.
        \return line that goes through position 'vec' with angle 'angle'. */
    public static Line makeLineFromPositionAndAngle( Position vec, double angle ){
      // calculate point somewhat further in direction 'angle' and make
      // line from these two points.
      return makeLineFromTwoPoints(vec, vec.sum( new Position(1,angle)));
    }

    /*! This method calculates the intersection points between the current line
        and the circle specified with as center 'posCenter' and radius 'dRadius'.
        The number of solutions are returned and the corresponding points are put
        in the third and fourth argument of the method
        \param c circle with which intersection points should be found
        \param posSolution1 first intersection (if any)
        \param posSolution2 second intersection (if any) */
    public int getCircleIntersectionPoints( Circle c, Position p1, Position p2){    
        int    iSol;
        double dSol1=0, dSol2=0;
        double h = c.getPosCenter().getPositionX();
        double k = c.getPosCenter().getPositionY();
        
        p1 = new Position();
        p2 = new Position();
        
        // line:   x = -c/b (if a = 0)
        // circle: (x-h)^2 + (y-k)^2 = r^2, with h = center.x and k = center.y
        // fill in:(-c/b-h)^2 + y^2 -2ky + k^2 - r^2 = 0
        //         y^2 -2ky + (-c/b-h)^2 + k^2 - r^2 = 0
        // and determine solutions for y using abc-formula
        if( Math.abs(m_a) < Function.EPSILON ){
            iSol = Function.abcFormula( 1, -2*k, ((-m_c/m_b) - h)*((-m_c/m_b) - h)
                      + k*k - c.getRadius()*c.getRadius(), dSol1, dSol2);
            p1.setPosition((-m_c/m_b), dSol1);
            p2.setPosition((-m_c/m_b), dSol2);
            return iSol;
        }

        // ay + bx + c = 0 => y = -b/a x - c/a, with da = -b/a and db = -c/a
        // circle: (x-h)^2 + (y-k)^2 = r^2, with h = center.x and k = center.y
        // fill in:x^2 -2hx + h^2 + (da*x-db)^2 -2k(da*x-db) + k^2 - r^2 = 0
        //         x^2 -2hx + h^2 + da^2*x^2 + 2da*db*x + db^2 -2k*da*x -2k*db
        //                                                         + k^2 - r^2 = 0
        //       (1+da^2)*x^2 + 2(da*db-h-k*da)*x + h2 + db^2  -2k*db + k^2 - r^2 = 0
        // and determine solutions for x using abc-formula
        // fill in x in original line equation to get y coordinate
        double da = -m_b/m_a;
        double db = -m_c/m_a;

        double dA = 1 + da*da;
        double dB = 2*( da*db - h - k*da );
        double dC = h*h + db*db-2*k*db + k*k - c.getRadius()*c.getRadius();

        iSol = Function.abcFormula( dA, dB, dC, dSol1, dSol2);

        p1.setPosition( dSol1, da*dSol1 + db );
        p2.setPosition( dSol2, da*dSol2 + db );
        return iSol;
    }    
    
    /*! This method returns the closest point on a line to a given position.
        \param pos point to which closest point should be determined
        \return VecPosition closest point on line to 'pos'. */
    public Position getPointOnLineClosestTo( Position pos ){
      Line l2 = getTangentLine( pos );  // get tangent line
      return getIntersection( l2 );     // and intersection between the two lines
    } 
    
    /*! This method returns the tangent line to a VecPosition. This is the line
        between the specified position and the closest point on the line to this
        position.
        \param pos VecPosition point with which tangent line is calculated.
        \return Line line tangent to this position */
    public Line getTangentLine( Position pos ){
      // ay + bx + c = 0 -> y = (-b/a)x + (-c/a)
      // tangent: y = (a/b)*x + C1 -> by - ax + C2 = 0 => C2 = ax - by
      // with pos.y = y, pos.x = x
      return new Line( m_b, -m_a, m_a*pos.getPositionX() - m_b*pos.getPositionY() );
    }    
    
    /*! This method returns the intersection point between the current Line and
        the specified line.
        \param line line with which the intersection should be calculated.
        \return VecPosition position that is the intersection point. */
    public Position getIntersection( Line line  ){
      Position pos = null;
      double x, y;
      if( ( m_a / m_b ) ==  (line.getACoefficient() / line.getBCoefficient() ))
        return pos; // lines are parallel, no intersection
      if( m_a == 0 )            // bx + c = 0 and a2*y + b2*x + c2 = 0 ==> x = -c/b
      {                          // calculate x using the current line
        x = -m_c/m_b;                // and calculate the y using the second line
        y = line.getYGivenX(x);
      }
      else if( line.getACoefficient() == 0 )
      {                         // ay + bx + c = 0 and b2*x + c2 = 0 ==> x = -c2/b2
       x = -line.getCCoefficient()/line.getBCoefficient(); // calculate x using
       y = getYGivenX(x);       // 2nd line and calculate y using current line
      }
      // ay + bx + c = 0 and a2y + b2*x + c2 = 0
      // y = (-b2/a2)x - c2/a2
      // bx = -a*y - c =>  bx = -a*(-b2/a2)x -a*(-c2/a2) - c ==>
      // ==> a2*bx = a*b2*x + a*c2 - a2*c ==> x = (a*c2 - a2*c)/(a2*b - a*b2)
      // calculate x using the above formula and the y using the current line
      else
      {
        x = (m_a*line.getCCoefficient() - line.getACoefficient()*m_c)/
                        (line.getACoefficient()*m_b - m_a*line.getBCoefficient());
        y = getYGivenX(x);
      }

      return new Position( x, y );
    }    
    
    /*! This method calculates the y coordinate given the x coordinate
        \param x coordinate
        \return y coordinate on this line */
    public double getYGivenX( double x ){
     if( m_a == 0 )
     {
       return 0;
     }
      // ay + bx + c = 0 ==> ay = -(b*x + c)/a
      return -(m_b*x+m_c)/m_a;
    }

    /*! This method calculates the x coordinate given the x coordinate
        \param y coordinate
        \return x coordinate on this line */
    public double getXGivenY( double y ){
     if( m_b == 0 )
     {
       return 0;
     }
      // ay + bx + c = 0 ==> bx = -(a*y + c)/a
      return -(m_a*y+m_c)/m_b;
    }  
    
    /*! This method determines whether the projection of a point on the
        current line lies between two other points ('point1' and 'point2')
        that lie on the same line.

        \param pos point of which projection is checked.
        \param point1 first point on line
        \param point2 second point on line
        \return true when projection of 'pos' lies between 'point1' and 'point2'.*/
    public boolean isInBetween( Position pos, Position point1, Position point2){
      pos          = getPointOnLineClosestTo( pos ); // get closest point
      double dDist = point1.getDistanceTo( point2 ); // get distance between 2 pos

      // if the distance from both points to the projection is smaller than this
      // dist, the pos lies in between.
      return pos.getDistanceTo( point1 ) <= dDist &&
             pos.getDistanceTo( point2 ) <= dDist;
    }    
    
    /*! This method returns the distance between a specified position and the
        closest point on the given line.
        \param pos position to which distance should be calculated
        \return double indicating the distance to the line. */
    public double getDistanceWithPoint( Position pos ){
      return pos.getDistanceTo( getPointOnLineClosestTo( pos ) );
    }    
}
